/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "google/protobuf/descriptor.pb.h"
#include "google/protobuf/dynamic_message.h"
#include "protobuf_util.h"
#include "napi_util.h"
#include "logger.h"
#include <cstdint>


bool JsToMessageInt32(const napi_env env, google::protobuf::Message *msg,
                      const google::protobuf::FieldDescriptor *field, napi_value value, bool is_repeated = false) {
  int32_t val;
  CHECK_COND(NapiUtil::GetInt32(env, value, &val) == napi_ok, false, "js to message error,get int32 error");
  auto *reflection = msg->GetReflection();
  if (is_repeated) {
    reflection->AddInt32(msg, field, val);
  } else {
    LOGD("current field is %{public}s", std::string(field->name()).c_str());
    reflection->SetInt32(msg, field, val);
  }
  return true;
}

bool JsToMessageEnum(const napi_env env, google::protobuf::Message *msg,
                      const google::protobuf::FieldDescriptor *field, napi_value value, bool is_repeated = false) {

  return true;
}
// 可能是number也可能是bigint
bool JsToMessageInt64(const napi_env env, google::protobuf::Message *msg,
                      const google::protobuf::FieldDescriptor *field, napi_value value, bool is_repeated = false) {
  napi_valuetype value_type = napi_undefined;
  CHECK_COND(napi_typeof(env, value, &value_type) == napi_ok, false, "Failed to get type");
  int64_t val;
  CHECK_COND(NapiUtil::GetInt64(env, value, &val) == napi_ok, false, "js to message error,get int64 error");
  auto *reflection = msg->GetReflection();
  if (is_repeated) {
    reflection->AddInt64(msg, field, val);
  } else {
    reflection->SetInt64(msg, field, val);
  }
  return true;
}

bool JsToMessageDouble(const napi_env env, google::protobuf::Message *msg,
                       const google::protobuf::FieldDescriptor *field, napi_value value, bool is_repeated = false) {
  double val;
  CHECK_COND(NapiUtil::GetDouble(env, value, &val) == napi_ok, false, "js to message error,get double error");
  auto *reflection = msg->GetReflection();
  if (is_repeated) {
    reflection->AddDouble(msg, field, val);
  } else {
    reflection->SetDouble(msg, field, val);
  }
  return true;
}

bool JsToMessageFloat(const napi_env env, google::protobuf::Message *msg,
                      const google::protobuf::FieldDescriptor *field, napi_value value, bool is_repeated = false) {
  float val;
  CHECK_COND(NapiUtil::GetFloat(env, value, &val) == napi_ok, false, "js to message error,get float error");
  auto *reflection = msg->GetReflection();
  if (is_repeated) {
    reflection->AddFloat(msg, field, val);
  } else {
    reflection->SetFloat(msg, field, val);
  }
  return true;
}

bool JsToMessageUint32(const napi_env env, google::protobuf::Message *msg,
                       const google::protobuf::FieldDescriptor *field, napi_value value, bool is_repeated = false) {
  uint32_t val;
  CHECK_COND(NapiUtil::GetUInt32(env, value, &val) == napi_ok, false, "js to message error,get uint32 error");
  auto *reflection = msg->GetReflection();
  if (is_repeated) {
    reflection->AddUInt32(msg, field, val);
  } else {
    reflection->SetUInt32(msg, field, val);
  }
  return true;
}

bool JsToMessageUint64(const napi_env env, google::protobuf::Message *msg,
                       const google::protobuf::FieldDescriptor *field, napi_value value, bool is_repeated = false) {
  uint64_t val;
  CHECK_COND(NapiUtil::GetUInt64(env, value, &val) == napi_ok, false, "js to message error,get uint64 error");
  auto *reflection = msg->GetReflection();
  if (is_repeated) {
    reflection->AddUInt64(msg, field, val);
  } else {
    reflection->SetUInt64(msg, field, val);
  }
  return true;
}

bool JsToMessageSint32(const napi_env env, google::protobuf::Message *msg,
                       const google::protobuf::FieldDescriptor *field, napi_value value, bool is_repeated = false) {
  int32_t val;
  CHECK_COND(NapiUtil::GetInt32(env, value, &val) == napi_ok, false, "js to message error,get int32 error");
  auto *reflection = msg->GetReflection();
  if (is_repeated) {
    reflection->AddInt32(msg, field, val);
  } else {
    reflection->SetInt32(msg, field, val);
  }
  return true;
}

bool JsToMessageSint64(const napi_env env, google::protobuf::Message *msg,
                       const google::protobuf::FieldDescriptor *field, napi_value value, bool is_repeated = false) {
  int64_t val;
  CHECK_COND(NapiUtil::GetInt64(env, value, &val) == napi_ok, false, "js to message error,get uint64 error");
  auto *reflection = msg->GetReflection();
  if (is_repeated) {
    reflection->AddInt64(msg, field, val);
  } else {
    reflection->SetInt64(msg, field, val);
  }
  return true;
}

bool JsToMessageFixed32(const napi_env env, google::protobuf::Message *msg,
                        const google::protobuf::FieldDescriptor *field, napi_value value, bool is_repeated = false) {
  uint32_t val;
  CHECK_COND(NapiUtil::GetUInt32(env, value, &val) == napi_ok, false, "js to message error,get uint32 error");
  auto *reflection = msg->GetReflection();
  if (is_repeated) {
    reflection->AddUInt32(msg, field, val);
  } else {
    reflection->SetUInt32(msg, field, val); // for循环获取
  }
  return true;
}

bool JsToMessageFixed64(const napi_env env, google::protobuf::Message *msg,
                        const google::protobuf::FieldDescriptor *field, napi_value value, bool is_repeated = false) {
  uint64_t val;
  CHECK_COND(NapiUtil::GetUInt64(env, value, &val) == napi_ok, false, "js to message error,get uint64 error");
  auto *reflection = msg->GetReflection();
  if (is_repeated) {
    reflection->AddUInt64(msg, field, val);
  } else {
    reflection->SetUInt64(msg, field, val);
  }
  return true;
}

bool JsToMessageSFixed32(const napi_env env, google::protobuf::Message *msg,
                         const google::protobuf::FieldDescriptor *field, napi_value value, bool is_repeated = false) {
  int32_t val;
  CHECK_COND(NapiUtil::GetInt32(env, value, &val) == napi_ok, false, "js to message error,get int32 error");
  auto *reflection = msg->GetReflection();
  if (is_repeated) {
    reflection->AddInt32(msg, field, val);
  } else {
    reflection->SetInt32(msg, field, val);
  }
  return true;
}

bool JsToMessageSFixed64(const napi_env env, google::protobuf::Message *msg,
                         const google::protobuf::FieldDescriptor *field, napi_value value, bool is_repeated = false) {
  int64_t val;
  CHECK_COND(NapiUtil::GetInt64(env, value, &val) == napi_ok, false, "js to message error,get uint64 error");
  auto *reflection = msg->GetReflection();
  if (is_repeated) {
    reflection->AddInt64(msg, field, val);
  } else {
    reflection->SetInt64(msg, field, val);
  }
  return true;
}

bool JsToMessageBool(const napi_env env, google::protobuf::Message *msg, const google::protobuf::FieldDescriptor *field,
                     napi_value value, bool is_repeated = false) {
  bool val;
  CHECK_COND(NapiUtil::GetBool(env, value, &val) == napi_ok, false, "js to message error,get bool error");
  auto *reflection = msg->GetReflection();
  if (is_repeated) {
    reflection->AddBool(msg, field, val);
  } else {
    reflection->SetBool(msg, field, val);
  }
  return true;
}

bool JsToMessageString(const napi_env env, google::protobuf::Message *msg,
                       const google::protobuf::FieldDescriptor *field, napi_value value, bool is_repeated = false) {
  std::string val;
  CHECK_COND(NapiUtil::GetStringUtf8(env, value, &val) == napi_ok, false, "js to message error,get string error");
  auto *reflection = msg->GetReflection();
  if (is_repeated) {
    reflection->AddString(msg, field, val);
  } else {
    reflection->SetString(msg, field, val);
  }
  return true;
}

// 使用std::string来表示bytes
bool JsToMessageBytes(const napi_env env, google::protobuf::Message *msg,
                      const google::protobuf::FieldDescriptor *field, napi_value value, bool is_repeated = false) {
  size_t length;
  void *data;
  size_t byte_offset;
  CHECK_COND(NapiUtil::GetUint8Array(env, value, &length, &data, nullptr, &byte_offset) == napi_ok, false,
             "js to message error,get Uint8Array error");
  auto *reflection = msg->GetReflection();

  uint8_t *actual_data = static_cast<uint8_t *>(data) + byte_offset;

  if (is_repeated) {
    reflection->AddString(msg, field, std::string(reinterpret_cast<char *>(actual_data), length));
  } else {
    reflection->SetString(msg, field, std::string(reinterpret_cast<char *>(actual_data), length));
  }
  return true;
}

bool JsToMessageGroup(const napi_env env, google::protobuf::Message *msg,
                      const google::protobuf::FieldDescriptor *field, napi_value value, bool is_repeated = false) {
  LOGW("not support group,filed is %{public}s", field->name());
  return true;
}

bool JsToMessageMessage(const napi_env env, google::protobuf::Message *msg,
                        const google::protobuf::FieldDescriptor *field, napi_value value, bool is_repeated = false) {


  protobuf_util::ConverterSingleton &instance = protobuf_util::ConverterSingleton::getInstance();
  LOGI("JsToMessageMessage ,field name is %{public}s ", std::string(field->name()).c_str());
  if (field->is_map()) {
    return true; // TODO map解析
  }
  if (!is_repeated) {
    google::protobuf::Message *property_message = msg->GetReflection()->MutableMessage(msg, field);
    instance.JsToMessage(env, value, property_message);
  } else {
    uint32_t length;
    CHECK_COND(napi_get_array_length(env, value, &length) == napi_ok, false,
               "JsToMessageMessage value is not array,filed name is %{public}s", std::string(field->name()).c_str());
    for (uint32_t i = 0; i < length; i++) {
      google::protobuf::Message *property_message = msg->GetReflection()->AddMessage(msg, field);
      instance.JsToMessage(env, value, property_message);
    }
  }
  return true;
}

protobuf_util::ConverterSingleton &protobuf_util::ConverterSingleton::getInstance() {
  static ConverterSingleton instance;
  static bool isInitialized = []() {
    auto &jsToMessageFunctionMap = instance.getJsToMessageFunctionMap();
    jsToMessageFunctionMap[google::protobuf::FieldDescriptor::TYPE_DOUBLE] = JsToMessageDouble;
    jsToMessageFunctionMap[google::protobuf::FieldDescriptor::TYPE_FLOAT] = JsToMessageFloat;
    jsToMessageFunctionMap[google::protobuf::FieldDescriptor::TYPE_INT32] = JsToMessageInt32;
    jsToMessageFunctionMap[google::protobuf::FieldDescriptor::TYPE_INT64] = JsToMessageInt64;
    jsToMessageFunctionMap[google::protobuf::FieldDescriptor::TYPE_UINT32] = JsToMessageUint32;
    jsToMessageFunctionMap[google::protobuf::FieldDescriptor::TYPE_UINT64] = JsToMessageUint64;
    jsToMessageFunctionMap[google::protobuf::FieldDescriptor::TYPE_SINT32] = JsToMessageInt32;
    jsToMessageFunctionMap[google::protobuf::FieldDescriptor::TYPE_SINT64] = JsToMessageInt64;
    jsToMessageFunctionMap[google::protobuf::FieldDescriptor::TYPE_FIXED32] = JsToMessageFixed32;
    jsToMessageFunctionMap[google::protobuf::FieldDescriptor::TYPE_FIXED64] = JsToMessageFixed64;
    jsToMessageFunctionMap[google::protobuf::FieldDescriptor::TYPE_SFIXED32] = JsToMessageSFixed32;
    jsToMessageFunctionMap[google::protobuf::FieldDescriptor::TYPE_SFIXED64] = JsToMessageSFixed64;
    jsToMessageFunctionMap[google::protobuf::FieldDescriptor::TYPE_BOOL] = JsToMessageBool;
    jsToMessageFunctionMap[google::protobuf::FieldDescriptor::TYPE_STRING] = JsToMessageString;
    jsToMessageFunctionMap[google::protobuf::FieldDescriptor::TYPE_BYTES] = JsToMessageBytes;
    jsToMessageFunctionMap[google::protobuf::FieldDescriptor::TYPE_MESSAGE] = JsToMessageMessage; // TODO
    jsToMessageFunctionMap[google::protobuf::FieldDescriptor::TYPE_GROUP] = JsToMessageGroup;
    // 枚举处理，使用number
    jsToMessageFunctionMap[google::protobuf::FieldDescriptor::TYPE_ENUM] = JsToMessageEnum;
    LOGI("add js to message function map success ,map size is %{public}d", jsToMessageFunctionMap.size());
    return true;
  }();
  return instance;
}

bool protobuf_util::ConverterSingleton::MessageToJs(napi_env env, google::protobuf::Message *msg, napi_value value) {
  return true;
}

bool ConvertSingleMessage(napi_env env, napi_value prop_value, const google::protobuf::Reflection *reflection,
                          const google::protobuf::FieldDescriptor *field, google::protobuf::Message *msg) {
  google::protobuf::Message *_msg = reflection->MutableMessage(msg, field);
  protobuf_util::ConverterSingleton &instance = protobuf_util::ConverterSingleton::getInstance();
  return instance.JsToMessage(env, prop_value, _msg);
}

bool protobuf_util::ConverterSingleton::JsToMessage(napi_env env, napi_value value, google::protobuf::Message *msg) {
  CHECK_COND(NapiUtil::CheckObject(env, value), false, "value is not object");
  const google::protobuf::Descriptor *desc = msg->GetDescriptor();
  const google::protobuf::Reflection *reflection = msg->GetReflection();
  if (!desc || !reflection) {
    LOGE("Protobuf message has no descriptor");
    napi_throw_error(env, nullptr, "Protobuf message has no descriptor");
    return false;
  }
  napi_value prop_names;
  CHECK_COND(napi_get_property_names(env, value, &prop_names) == napi_ok, false, "Failed to get property names");
  uint32_t prop_length;
  CHECK_COND(napi_get_array_length(env, prop_names, &prop_length) == napi_ok, false, "Failed to get property count");
  for (uint32_t i = 0; i < prop_length; i++) {
    napi_value prop_name;
    CHECK_COND(napi_get_element(env, prop_names, i, &prop_name) == napi_ok, false,
               "Failed to get element ,index is %{public}d", i);
    std::string prop_name_str;
    CHECK_COND(NapiUtil::GetStringUtf8(env, prop_name, &prop_name_str) == napi_ok, false,
               "Failed to get property name ,index is %{public}d", i);

    std::string prop_name_cover_str = PropertyToMessage(prop_name_str);
    const google::protobuf::FieldDescriptor *filed = desc->FindFieldByName(prop_name_cover_str);
    if (!filed) {
      LOGD("not exist filed,property name is %{public}s ,cover result is %{public}s", prop_name_str.c_str(),
           prop_name_cover_str.c_str());
      continue;
    }
    napi_value prop_value;
    CHECK_COND(napi_get_named_property(env, value, prop_name_str.c_str(), &prop_value) == napi_ok, false,
               "Failed to get property value ,property name is %{public}s", i);
    protobuf_util::ConverterSingleton &instance = protobuf_util::ConverterSingleton::getInstance();
    auto map = instance.getJsToMessageFunctionMap();
    auto file_type = filed->type();
    auto it = map.find(file_type);
    if (it == map.end()) {
      LOGW("not exist cover js to message function ,type is %{public}d", file_type);
      continue;
    }
    auto func = map[file_type];
    bool success;
    if (!filed->is_repeated()) {
      success = func(env, msg, filed, prop_value, false);
    } else {
      if (filed->is_map()) {
        LOGE("not support map ,name is %{public}s ", prop_name_str.c_str());
        continue;
      }
      uint32_t array_length;
      CHECK_COND((napi_get_array_length(env, prop_value, &array_length) == napi_ok), false,
                 "error get arr length ,error name is %{public}s ", prop_name_str.c_str());
      if (array_length == 0) {
        LOGD("array length is 0,prop name is  %{public}s ", prop_name_str.c_str());
        continue;
      }
      for (uint32_t j = 0; j < array_length; j++) {
        napi_value element;
        CHECK_COND((napi_get_element(env, prop_value, i, &element) == napi_ok), false,
                   "error get element error name is %{public}s ,index is %{public}d", prop_name_str.c_str(), j);
        success = func(env, msg, filed, element, true);
        if (!success) {
          LOGE("error get element error name is %{public}s ,index is %{public}d", prop_name_str.c_str(), j);
        }
      }
    }
    if (!success) {
      LOGE("cover js to message error ,type is %{public}d,current field name is %{public}s", file_type,
           prop_name_cover_str.c_str());
      continue;
    }
  }

  return true;
}
std::string snakeToCamel(const std::string &snake) {
  std::string camel;
  bool nextUpper = false; // 标记下一个字符是否需要大写
  for (char c : snake) {
    if (c == '_') {
      nextUpper = true; // 遇到下划线，标记下一个字符大写
    } else {
      if (nextUpper) {
        camel += std::toupper(c); // 下划线后字符转为大写
        nextUpper = false;
      } else {
        camel += c; // 其他字符直接添加
      }
    }
  }
  return camel;
}
std::string camelToSnake(const std::string &camel) {
  std::string snake;
  for (size_t i = 0; i < camel.size(); ++i) {
    char c = camel[i];
    if (std::isupper(c)) {
      // 大写字母前添加下划线（首字母大写则不添加下划线，直接转小写）
      if (i > 0) {
        snake += '_';
      }
      snake += std::tolower(c); // 大写转小写
    } else {
      snake += c; // 小写字母直接添加
    }
  }
  return snake;
}
std::string protobuf_util::ConverterSingleton::PropertyToJs(std::string propertyName) { return propertyName; }
std::string protobuf_util::ConverterSingleton::PropertyToMessage(std::string propertyName) {
  return camelToSnake(propertyName);
}


google::protobuf::DescriptorPool &protobuf_util::getGlobalDescPool() {
  static google::protobuf::DescriptorPool instance;
  return instance;
}
